package com.fxz.oauth.util;

import com.fxz.oauth.config.AppProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author Fxz
 * @version 1.0
 * @date 2021-08-04 13:51
 */
@RequiredArgsConstructor
@Component
public class JwtUtil {

    /**
     * 用于签名访问令牌的密钥
     */
    public static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS512);
    /**
     * 用于签名刷新令牌的密钥
     */
    public static final Key refreshKey = Keys.secretKeyFor(SignatureAlgorithm.HS512);
    /**
     * Jwt相关属性的配置
     */
    private final AppProperties appProperties;

    /**
     * 创建访问token
     *
     * @param userDetails
     * @return
     */
    public String createAccessToken(UserDetails userDetails) {
        return createJwtToken(userDetails, key, appProperties.getJwt().getAccessTokenExpireTime());
    }

    /**
     * 创建刷新token
     *
     * @param userDetails
     * @return
     */
    public String createRefreshToken(UserDetails userDetails) {
        return createJwtToken(userDetails, refreshKey, appProperties.getJwt().getRefreshTokenExpireTime());
    }

    /**
     * 使用刷新令牌创建访问令牌
     *
     * @param token
     * @return
     */
    public String createAccessTokenWithRefreshToken(String token) {
        return parseClaims(token, refreshKey)
                .map(claims -> Jwts.builder()
                        .setClaims(claims)
                        .setExpiration(new Date(System.currentTimeMillis() + appProperties.getJwt().getAccessTokenExpireTime()))
                        .signWith(key, SignatureAlgorithm.HS512).compact())
                .orElseThrow(() -> new AccessDeniedException("访问被拒绝!"));
    }

    /**
     * 根据刷新token 解析出Claims
     * @param jwtToken
     * @param signKey
     * @return
     */
    public Optional<Claims> parseClaims(String jwtToken, Key signKey) {
        try {
            val claims = Jwts.parserBuilder().setSigningKey(signKey).build().parseClaimsJws(jwtToken).getBody();
            return Optional.of(claims);
        } catch (Exception e) {
            return Optional.empty();
        }
    }

    /**
     * 校验建访问token(不考虑过期)
     *
     * @param token AccessToken
     * @return
     */
    public boolean validateAccessTokenWithoutExpiration(String token) {
        return validateToken(token, key, false);
    }

    /**
     * 校验建访问token
     *
     * @param token AccessToken
     * @return
     */
    public boolean validateAccessToken(String token) {
        return validateToken(token, key, true);
    }

    /**
     * 校验刷新token
     *
     * @param token RefreshToken
     * @return
     */
    public boolean validateRefreshToken(String token) {
        return validateToken(token, refreshKey, true);
    }

    /**
     * 校验token
     */
    public boolean validateToken(String token, Key key, boolean isExpired) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parse(token);
            return true;
        } catch (Exception e) {
            if (e instanceof ExpiredJwtException) {
                return isExpired;
            }
            return false;
        }
    }

    /**
     * 创建token
     *
     * @param userDetails
     * @param key
     * @param timeToExpire
     * @return
     */
    public String createJwtToken(UserDetails userDetails, Key key, long timeToExpire) {
        long now = System.currentTimeMillis();
        return Jwts.builder()
                .setId("fxz")
                .claim("authorities", userDetails.getAuthorities().stream()
                        .map(authority -> authority.getAuthority())
                        .collect(Collectors.toList()))
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date(now))
                .setExpiration(new Date(now + 60_000))
                .signWith(key, SignatureAlgorithm.HS512)
                .compact();
    }
}
